Overview
In this weeks assignment we will show how to create maps which
contain geo spacial information. First, we will build a scatter map
which shows information about various gas stations in the United States.
After that, we will narrow down our view to the city of Philadelphia;
analyzing crime data from the years of 2014-2015. Let’s see what kind of
story we can extract by visualizing our data sets.
Gas Station Source
Data
It’s important to describe any data set before working with it, so
let’s begin by analyzing the raw gas station data. In the previous weeks
assignements, we have loaded our data via having the csv in a local
working directory. For this week, data was uploaded to my github. Here
is how we can load the data via https.
gas_data <- read.csv("https://jmartin12.github.io/STAT553/data/gas_station_data.csv", header = TRUE)
Simple as that! First, let’s view the raw data in it’s table format,
along with seeing how many rows we have.
X site_row_id STATE county ADDRESS CITY ycoord xcoord
1 1 1-3R8J-494 CA Los Angeles 37120 47TH ST E PALMDALE 34.55584 -118.0452
2 2 1-3R8J-362 WA Franklin 1212 N 4TH AVE PASCO 46.23890 -119.0950
SITE_DESCRIPTION service_or_fuel diesel
1 Los Angeles-Long Beach-Santa Ana CA Fuel Y
2 Kennewick-Pasco-Richland WA Fuel N
twentyfour_hour_flag car_wash truckstop_flag description PUMP_TECH POC HIFCA
1 Y N Y URBAN O 0 0
2 N N N URBAN O 0 1
ZIPnew POCAGE POCGAP ZIPPOC HFG MSA dist.to.poc cate.poc.density
1 93552 NA NA 0 1 4480 8.2275601 (-1e-06,1]
2 99301 NA NA 1 0 6740 0.2788194 (-1e-06,1]
cate.poc.age cate.poc.age.20 cate.poc.intensity cate.poc.intensity.tot
1 (15,140] (15,140] (5,Inf] (8,Inf]
2 (15,140] (15,140] (5,Inf] (8,Inf]
MSA_POC MSA_POC.1
1 1 1
2 0 0
[1] 72798
While there are many columns in this data set, what’s important to
notice for this weeks lesson is that we have two columns titled as
ycoord and xcoord. These columns represent the
coordinates in latitude and longitude,
respectively. We will use both of these columns later when creating our
map.
It’s also important to note the size of the data set; a mere 70K
rows! If we plotted all of these on our map, it would be cluttered and
difficult to interpret. Let’s reduce our data set size to simply 500
random rows. The code below demonstrates how we do this:
# Set the seed so you get the same rows I got.
set.seed(123)
# Get the 500 random rows
reduced_gas_data <- gas_data[sample(nrow(gas_data), 500), ]
# Print the row count
print(nrow(reduced_gas_data))
[1] 500
Success! We have reduced our data to 500 random rows.
Creating a Basic Geo
Spacial Informative Scatter Map
For this week, we will focus on creating maps using the
leaflet library. Please view the generated map, while
reading an explanation of the code below.
# Create a leaflet map
gas_map <- leaflet() %>%
addTiles() %>%
setView(lng = -98.5795, lat = 39.8283, zoom = 3)
# Add markers for each gas station
for (i in 1:nrow(reduced_gas_data)) {
gas_meta = paste('state: ',reduced_gas_data$STATE[i], '\n county: ', reduced_gas_data$county[i], '\n city: ' ,reduced_gas_data$CITY[i] ,'\n address: ', reduced_gas_data$ADDRESS[i])
gas_map <- addMarkers(
map = gas_map,
lng = reduced_gas_data$xcoord[i],
lat = reduced_gas_data$ycoord[i],
label = htmltools::HTML(htmlEscape(gas_meta))
)
}
gas_map
Walking through the code, we first start out with
gas_map <- leaflet() %>% addTiles() %>% setView(lng = -98.5795, lat = 39.8283, zoom = 3).
The one thing that should be noted is that the default view was taken as
the center point of the USA. Since our reduced data was randomly taken
from the original 70K rows, using the mean of the lng and lat values
will not give us a consistently good view, because the view is then
subject to what gas stations we have randomly chosen. To circumvent
this, the center point of the USA was used.
Aside to that, we then use a basic for loop control
structure, in combination with the paste(...) function to
add location metadata using the city, state,
county, and address from our data set.
This is a primitive example of how the leaflet library
can be used to graph geographical information. Let’s move onto a more
advanced example so that we are able to extract more useful information
and decipher a story therein.
Philly Crime
Data
First, we will need a new data set that is hosted on my github.
philly_data <- read.csv("https://jmartin12.github.io/STAT553/data/PhillyCrimeSince2015.csv", header = TRUE)
An example few rows are given:
dc_key race sex fatal date
1 2.02422E+11 Black (Non-Hispanic) Female Nonfatal 3/3/2024 14:49
2 2.02426E+11 Hispanic (Black or White) Male Nonfatal 3/1/2024 22:18
has_court_case age street_name block_number zip_code council_district
1 No 20 N COLORADO ST 2500 19132 5
2 No 58 N FRANKLIN ST 2600 19133 5
police_district neighborhood house_district
1 22 Sharswood-Stanton 181
2 26 Northern Liberties-West Kensington 197
senate_district school_catchment lng lat
1 3 Tanner G. Duckrey School -75.16060 39.99166
2 3 John F. Hartranft School -75.14468 39.99152
The total amount of rows are:
[1] 15520
Philly is definitely known for crime in certain areas. Let’s focus in
on the year 2023. To accomplish this, we will have to perform some data
transformations against the original date column. First,
the following code parses out the year portion, and stores
it in a vector.
years <- character(nrow(philly_data))
for (i in 1:nrow(philly_data)) {
date_string <- philly_data$date[i]
date_object <- strptime(date_string, "%m/%d/%Y %H:%M")
years[i] <- format(date_object, "%Y")
}
head(years)
[1] "2024" "2024" "2024" "2024" "2024" "2024"
Great! Now we have all the years. Let’s add it as a new column to our
original philly crime dataset.
philly_data$year <- years
head(philly_data, 2)
dc_key race sex fatal date
1 2.02422E+11 Black (Non-Hispanic) Female Nonfatal 3/3/2024 14:49
2 2.02426E+11 Hispanic (Black or White) Male Nonfatal 3/1/2024 22:18
has_court_case age street_name block_number zip_code council_district
1 No 20 N COLORADO ST 2500 19132 5
2 No 58 N FRANKLIN ST 2600 19133 5
police_district neighborhood house_district
1 22 Sharswood-Stanton 181
2 26 Northern Liberties-West Kensington 197
senate_district school_catchment lng lat year
1 3 Tanner G. Duckrey School -75.16060 39.99166 2024
2 3 John F. Hartranft School -75.14468 39.99152 2024
Easy as that! We can see the year column is now added to the data
set. To narrow down a subset of 2023 data only, we can reduce our data
set by filtering on the newly added year column.
philly_data_2023 <- subset(philly_data, year == 2023)
head(philly_data_2023$date, 5)
[1] "12/31/2023 4:57" "12/31/2023 3:35" "12/31/2023 3:13" "12/30/2023 22:56"
[5] "12/30/2023 11:32"
Only the date column is shown in the above example – the
remaining columns are still stored in memory. We can clearly see there
are only dates in 2023.
Philly Crimes -
Mapped
Now that we have our 2023 data, let’s go ahead and use leafly to map
our data. The code below adds two additional columns to the dataset so
we can easily determine what colors to use, and in addition generating
the label information.
From there, a title along with a legend is added to the graph.
colors <- character(nrow(philly_data_2023))
labels <- character(nrow(philly_data_2023))
for (i in 1:nrow(philly_data_2023)) {
# Handle the colors
if (philly_data_2023$fatal[i] == 'Fatal') {
colors[i] <- '#000000'
}
else {
colors[i] <- '#CC79A7'
}
# Handle the info to display in the label
labels[i] <- paste('Gender: ', philly_data_2023$sex[i], 'Neighborhood: ', philly_data_2023$neighborhood[i], 'Block Number: ', philly_data_2023$block_number[i])
}
philly_data_2023$crime_type_color <- colors
philly_data_2023$label <- labels
## Map title
title <- tags$div( HTML('<font color = "darkred" size =4><b>Philly Fatal and Non-Fatal Crimes 2023</b></font>')
)
# Create a Leaflet map
m <- leaflet(philly_data_2023) %>%
addTiles() %>%
setView(lat = 40, lng = -75.1652, zoom = 11) %>%
addCircleMarkers(
lng = ~lng,
lat = ~lat,
color = ~crime_type_color,
radius = 3,
label = ~label,
labelOptions = labelOptions(noHide = FALSE, textOnly = FALSE)) %>%
addControl(title, position = "topright") %>%
addLegend(position = "bottomleft",
colors = c("#CC79A7", "#000000"),
labels= c("Non Fatal", "Fatal"),
title= "Crime Type",
opacity = 0.8)
m
Narration
It is particularly interesting to see the distribution of crimes
across the city of Philly. There are a few hotspots of fatal crimes. The
primary hotspot being the southwest region of the city, followed by a
smaller hotspot just north of the city.
The dataset itself can tell us more with different graphs. Let’s view
the % of crimes committed by gender.
# Count the number of crimes per gender
crimes_per_gender <- philly_data_2023 %>%
group_by(sex) %>%
summarise(crime_count = n())
# Calculate the percentage of each gender
crimes_per_gender$percentage <- (crimes_per_gender$crime_count / sum(crimes_per_gender$crime_count)) * 100
# Create a pie chart
p3 <- plot_ly(crimes_per_gender, labels = ~sex, values = ~percentage, type = 'pie') %>%
layout(title = "% of Crimes Committed by Males vs. Females")
# Print the plot
p3
It seems that males commit most of the crimes in the city of
Philadelphia. Something else of interest could be to analyze which
school districts have the highest crime count. In particular, parents
could find this data to be of use when determining what school district
to send their kids to.
# Count the number of crimes per school district
crimes_per_district <- philly_data_2023 %>%
group_by(school_catchment) %>%
summarise(crime_count = n()) %>%
top_n(10, crime_count)
# Create a bar graph
p <- plot_ly(crimes_per_district, y = ~school_catchment, x = ~crime_count, type = 'bar') %>%
layout(title = "Number of Crimes per School District in Philadelphia (2023)",
xaxis = list(title = "School District"),
yaxis = list((title = "Number of Crimes")))
# Print the plot
p
These ten schools are the schools with the highest number of reported
crimes, indicating a potentially higher crime rate in these areas. This
data could be influential when informing parents about the safety risks
associated with a particular school district, helping them make informed
decisions about their children’s education and well-being.
LS0tCnRpdGxlOiAiV2VlayA3IC0gR2VvIFNwYWNpYWwgRGF0YSBWaXN1YWxpemF0aW9uIgphdXRob3I6ICJKYWNvYiBNYXJ0aW4iCmRhdGU6ICJXZXN0IENoZXN0ZXIgVW5pdmVyc2l0eSAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OiAKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKICAgIHRvY19mbG9hdDogeWVzCiAgICBmaWdfd2lkdGg6IDYKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCiAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICB0aGVtZTogcmVhZGFibGUKICAgIGZpZ19oZWlnaHQ6IDQKLS0tCgpgYGB7PWh0bWx9CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CgpkaXYjVE9DIGxpIHsKICAgIGxpc3Qtc3R5bGU6bm9uZTsKICAgIGJhY2tncm91bmQtY29sb3I6bGlnaHRncmF5OwogICAgYmFja2dyb3VuZC1pbWFnZTpub25lOwogICAgYmFja2dyb3VuZC1yZXBlYXQ6bm9uZTsKICAgIGJhY2tncm91bmQtcG9zaXRpb246MDsKICAgIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOwogICAgY29sb3I6ICM3ODBjMGM7Cn0KCi8qIG1vdXNlIG92ZXIgbGluayAqLwpkaXYjVE9DIGE6aG92ZXIgewogIGNvbG9yOiByZWQ7Cn0KCi8qIHVudmlzaXRlZCBsaW5rICovCmRpdiNUT0MgYTpsaW5rIHsKICBjb2xvcjogYmx1ZTsKfQoKCgpoMS50aXRsZSB7CiAgZm9udC1zaXplOiAyNHB4OwogIGNvbG9yOiBEYXJrYmx1ZTsKICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgZm9udC1mYW1pbHk6IEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7CiAgZm9udC12YXJpYW50LWNhcHM6IG5vcm1hbDsKfQpoNC5hdXRob3IgeyAKICAgIGZvbnQtc2l6ZTogMThweDsKICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsKICBjb2xvcjogRGFya1JlZDsKICB0ZXh0LWFsaWduOiBjZW50ZXI7Cn0KaDQuZGF0ZSB7IAogIGZvbnQtc2l6ZTogMThweDsKICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsKICBjb2xvcjogRGFya0JsdWU7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9CmgxIHsKICAgIGZvbnQtc2l6ZTogMjRweDsKICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOwogICAgY29sb3I6IGRhcmtyZWQ7CiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7Cn0KaDIgewogICAgZm9udC1zaXplOiAxOHB4OwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogbmF2eTsKICAgIHRleHQtYWxpZ246IGxlZnQ7Cn0KCmgzIHsgCiAgICBmb250LXNpemU6IDE1cHg7CiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsKICAgIGNvbG9yOiBuYXZ5OwogICAgdGV4dC1hbGlnbjogbGVmdDsKfQoKaDQgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLwogICAgZm9udC1zaXplOiAxOHB4OwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogZGFya3JlZDsKICAgIHRleHQtYWxpZ246IGxlZnQ7Cn0KCi8qIHVudmlzaXRlZCBsaW5rICovCmE6bGluayB7CiAgY29sb3I6IGdyZWVuOwp9CgovKiB2aXNpdGVkIGxpbmsgKi8KYTp2aXNpdGVkIHsKICBjb2xvcjogZ3JlZW47Cn0KCi8qIG1vdXNlIG92ZXIgbGluayAqLwphOmhvdmVyIHsKICBjb2xvcjogcmVkOwp9CgovKiBzZWxlY3RlZCBsaW5rICovCmE6YWN0aXZlIHsKICBjb2xvcjogeWVsbG93Owp9Cgo8L3N0eWxlPgpgYGAKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMgY29kZSBjaHVuayBzcGVjaWZpZXMgd2hldGhlciB0aGUgUiBjb2RlLCB3YXJuaW5ncywgYW5kIG91dHB1dCAKIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuCm9wdGlvbnMocmVwb3MgPSBsaXN0KENSQU49Imh0dHA6Ly9jcmFuLnJzdHVkaW8uY29tLyIpKQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpCiAgIGxpYnJhcnkodGlkeXZlcnNlKQp9CmlmICghcmVxdWlyZSgia25pdHIiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpCiAgIGxpYnJhcnkoa25pdHIpCn0KaWYgKCFyZXF1aXJlKCJjb3dwbG90IikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygiY293cGxvdCIpCiAgIGxpYnJhcnkoY293cGxvdCkKfQppZiAoIXJlcXVpcmUoImxhdGV4MmV4cCIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoImxhdGV4MmV4cCIpCiAgIGxpYnJhcnkobGF0ZXgyZXhwKQp9CmlmICghcmVxdWlyZSgicGxvdGx5IikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikKICAgbGlicmFyeShwbG90bHkpCn0KaWYgKCFyZXF1aXJlKCJnYXBtaW5kZXIiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJnYXBtaW5kZXIiKQogICBsaWJyYXJ5KGdhcG1pbmRlcikKfQppZiAoIXJlcXVpcmUoInBuZyIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJwbmciKSAgICAgICAgICAgICAjIEluc3RhbGwgcG5nIHBhY2thZ2UKICAgIGxpYnJhcnkoInBuZyIpCn0KaWYgKCFyZXF1aXJlKCJSQ3VybCIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJSQ3VybCIpICAgICAgICAgICAjIEluc3RhbGwgUkN1cmwgcGFja2FnZQogICAgbGlicmFyeSgiUkN1cmwiKQp9CmlmICghcmVxdWlyZSgiY29sb3VycGlja2VyIikpIHsKICAgIGluc3RhbGwucGFja2FnZXMoImNvbG91cnBpY2tlciIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImNvbG91cnBpY2tlciIpCn0KaWYgKCFyZXF1aXJlKCJnaWZza2kiKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2lmc2tpIikgICAgICAgICAgICAgIAogICAgbGlicmFyeSgiZ2lmc2tpIikKfQppZiAoIXJlcXVpcmUoIm1hZ2ljayIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJtYWdpY2siKSAgICAgICAgICAgICAgCiAgICBsaWJyYXJ5KCJtYWdpY2siKQp9CmlmICghcmVxdWlyZSgiZ3JEZXZpY2VzIikpIHsKICAgIGluc3RhbGwucGFja2FnZXMoImdyRGV2aWNlcyIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImdyRGV2aWNlcyIpCn0KIyMjIGdncGxvdCBhbmQgZXh0ZW5zaW9ucwppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImdncGxvdDIiKQp9CmlmICghcmVxdWlyZSgiZ2dhbmltYXRlIikpIHsKICAgIGluc3RhbGwucGFja2FnZXMoImdnYW5pbWF0ZSIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImdnYW5pbWF0ZSIpCn0KaWYgKCFyZXF1aXJlKCJnZ3JpZGdlcyIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3JpZGdlcyIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImdncmlkZ2VzIikKfQppZiAoIXJlcXVpcmUoImdyYXBoaWNzIikpIHsKICAgIGluc3RhbGwucGFja2FnZXMoImdyYXBoaWNzIikgICAgICAgICAgICAgIAogICAgbGlicmFyeSgiZ3JhcGhpY3MiKQp9CmlmICghcmVxdWlyZSgidGlkeXIiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5ciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCiAgIGxpYnJhcnkodGlkeXIpCn0KaWYgKCFyZXF1aXJlKCJyZXNoYXBlMiIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoInJlc2hhcGUyIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKICAgbGlicmFyeShyZXNoYXBlMikKfQppZiAoIXJlcXVpcmUoImxlYWZsZXQiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJsZWFmbGV0IiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKICAgbGlicmFyeShsZWFmbGV0KQp9CmlmICghcmVxdWlyZSgiaHRtbHRvb2xzIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygiaHRtbHRvb2xzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKICAgbGlicmFyeShodG1sdG9vbHMpCn0KCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgCiAgICAgICAgICAgICAgICAgICAgICByZXN1bHQgPSBUUlVFLCAgIAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BKQpgYGAKCgojIyBPdmVydmlldwpJbiB0aGlzIHdlZWtzIGFzc2lnbm1lbnQgd2Ugd2lsbCBzaG93IGhvdyB0byBjcmVhdGUgbWFwcyB3aGljaCBjb250YWluIGdlbyBzcGFjaWFsIGluZm9ybWF0aW9uLiBGaXJzdCwgd2Ugd2lsbCBidWlsZCBhIHNjYXR0ZXIgbWFwIHdoaWNoIHNob3dzIGluZm9ybWF0aW9uIGFib3V0IHZhcmlvdXMgZ2FzIHN0YXRpb25zIGluIHRoZSBVbml0ZWQgU3RhdGVzLiBBZnRlciB0aGF0LCB3ZSB3aWxsIG5hcnJvdyBkb3duIG91ciB2aWV3IHRvIHRoZSBjaXR5IG9mIFBoaWxhZGVscGhpYTsgYW5hbHl6aW5nIGNyaW1lIGRhdGEgZnJvbSB0aGUgeWVhcnMgb2YgMjAxNC0yMDE1LiBMZXQncyBzZWUgd2hhdCBraW5kIG9mIHN0b3J5IHdlIGNhbiBleHRyYWN0IGJ5IHZpc3VhbGl6aW5nIG91ciBkYXRhIHNldHMuCgojIyBHYXMgU3RhdGlvbiBTb3VyY2UgRGF0YQpJdCdzIGltcG9ydGFudCB0byBkZXNjcmliZSBhbnkgZGF0YSBzZXQgYmVmb3JlIHdvcmtpbmcgd2l0aCBpdCwgc28gbGV0J3MgYmVnaW4gYnkgYW5hbHl6aW5nIHRoZSByYXcgZ2FzIHN0YXRpb24gZGF0YS4gCkluIHRoZSBwcmV2aW91cyB3ZWVrcyBhc3NpZ25lbWVudHMsIHdlIGhhdmUgbG9hZGVkIG91ciBkYXRhIHZpYSBoYXZpbmcgdGhlIGNzdiBpbiBhIGxvY2FsIHdvcmtpbmcgZGlyZWN0b3J5LiBGb3IgdGhpcyB3ZWVrLCBkYXRhIHdhcyB1cGxvYWRlZCB0byBteSBnaXRodWIuIEhlcmUgaXMgaG93IHdlIGNhbiBsb2FkIHRoZSBkYXRhIHZpYSBodHRwcy4gCgpgYGB7ciBlY2hvPVRSVUV9Cmdhc19kYXRhIDwtIHJlYWQuY3N2KCJodHRwczovL2ptYXJ0aW4xMi5naXRodWIuaW8vU1RBVDU1My9kYXRhL2dhc19zdGF0aW9uX2RhdGEuY3N2IiwgaGVhZGVyID0gVFJVRSkKYGBgCgpTaW1wbGUgYXMgdGhhdCEgRmlyc3QsIGxldCdzIHZpZXcgdGhlIHJhdyBkYXRhIGluIGl0J3MgdGFibGUgZm9ybWF0LCBhbG9uZyB3aXRoIHNlZWluZyBob3cgbWFueSByb3dzIHdlIGhhdmUuIAoKYGBge3IgZWNobz1GQUxTRX0KaGVhZChnYXNfZGF0YSwgMikKbnJvdyhnYXNfZGF0YSkKYGBgCgpXaGlsZSB0aGVyZSBhcmUgbWFueSBjb2x1bW5zIGluIHRoaXMgZGF0YSBzZXQsIHdoYXQncyBpbXBvcnRhbnQgdG8gbm90aWNlIGZvciB0aGlzIHdlZWtzIGxlc3NvbiBpcyB0aGF0IHdlIGhhdmUgdHdvIGNvbHVtbnMgdGl0bGVkIGFzIGB5Y29vcmRgIGFuZCBgeGNvb3JkYC4gVGhlc2UgY29sdW1ucyByZXByZXNlbnQgdGhlIGNvb3JkaW5hdGVzIGluIGBsYXRpdHVkZWAgYW5kIGBsb25naXR1ZGVgLCByZXNwZWN0aXZlbHkuIFdlIHdpbGwgdXNlIGJvdGggb2YgdGhlc2UgY29sdW1ucyBsYXRlciB3aGVuIGNyZWF0aW5nIG91ciBtYXAuCgpJdCdzIGFsc28gaW1wb3J0YW50IHRvIG5vdGUgdGhlIHNpemUgb2YgdGhlIGRhdGEgc2V0OyBhIG1lcmUgNzBLIHJvd3MhIElmIHdlIHBsb3R0ZWQgYWxsIG9mIHRoZXNlIG9uIG91ciBtYXAsIGl0IHdvdWxkIGJlIGNsdXR0ZXJlZCBhbmQgZGlmZmljdWx0IHRvIGludGVycHJldC4gTGV0J3MgcmVkdWNlIG91ciBkYXRhIHNldCBzaXplIHRvIHNpbXBseSA1MDAgcmFuZG9tIHJvd3MuIFRoZSBjb2RlIGJlbG93IGRlbW9uc3RyYXRlcyBob3cgd2UgZG8gdGhpczoKCmBgYHtyIGVjaG8gPSBUUlVFfQojIFNldCB0aGUgc2VlZCBzbyB5b3UgZ2V0IHRoZSBzYW1lIHJvd3MgSSBnb3QuCnNldC5zZWVkKDEyMykKCiMgR2V0IHRoZSA1MDAgcmFuZG9tIHJvd3MKcmVkdWNlZF9nYXNfZGF0YSA8LSBnYXNfZGF0YVtzYW1wbGUobnJvdyhnYXNfZGF0YSksIDUwMCksIF0KCiMgUHJpbnQgdGhlIHJvdyBjb3VudApwcmludChucm93KHJlZHVjZWRfZ2FzX2RhdGEpKQpgYGAKClN1Y2Nlc3MhIFdlIGhhdmUgcmVkdWNlZCBvdXIgZGF0YSB0byA1MDAgcmFuZG9tIHJvd3MuCgojIyBDcmVhdGluZyBhIEJhc2ljIEdlbyBTcGFjaWFsIEluZm9ybWF0aXZlIFNjYXR0ZXIgTWFwCkZvciB0aGlzIHdlZWssIHdlIHdpbGwgZm9jdXMgb24gY3JlYXRpbmcgbWFwcyB1c2luZyB0aGUgYGxlYWZsZXRgIGxpYnJhcnkuIFBsZWFzZSB2aWV3IHRoZSBnZW5lcmF0ZWQgbWFwLCB3aGlsZSByZWFkaW5nIGFuIGV4cGxhbmF0aW9uIG9mIHRoZSBjb2RlIGJlbG93LiAKCmBgYHtyIGVjaG89VFJVRX0KIyBDcmVhdGUgYSBsZWFmbGV0IG1hcApnYXNfbWFwIDwtIGxlYWZsZXQoKSAlPiUKICBhZGRUaWxlcygpICU+JQogIHNldFZpZXcobG5nID0gLTk4LjU3OTUsIGxhdCA9IDM5LjgyODMsIHpvb20gPSAzKQoKIyBBZGQgbWFya2VycyBmb3IgZWFjaCBnYXMgc3RhdGlvbgpmb3IgKGkgaW4gMTpucm93KHJlZHVjZWRfZ2FzX2RhdGEpKSB7CiAgZ2FzX21ldGEgPSBwYXN0ZSgnc3RhdGU6ICcscmVkdWNlZF9nYXNfZGF0YSRTVEFURVtpXSwgJ1xuIGNvdW50eTogJywgcmVkdWNlZF9nYXNfZGF0YSRjb3VudHlbaV0sICdcbiBjaXR5OiAnICxyZWR1Y2VkX2dhc19kYXRhJENJVFlbaV0gLCdcbiBhZGRyZXNzOiAnLCByZWR1Y2VkX2dhc19kYXRhJEFERFJFU1NbaV0pCiAgCiAgZ2FzX21hcCA8LSBhZGRNYXJrZXJzKAogICAgbWFwID0gZ2FzX21hcCwKICAgIGxuZyA9IHJlZHVjZWRfZ2FzX2RhdGEkeGNvb3JkW2ldLAogICAgbGF0ID0gcmVkdWNlZF9nYXNfZGF0YSR5Y29vcmRbaV0sCiAgICBsYWJlbCA9IGh0bWx0b29sczo6SFRNTChodG1sRXNjYXBlKGdhc19tZXRhKSkKICAgICkKfQoKZ2FzX21hcApgYGAKCldhbGtpbmcgdGhyb3VnaCB0aGUgY29kZSwgd2UgZmlyc3Qgc3RhcnQgb3V0IHdpdGggYGdhc19tYXAgPC0gbGVhZmxldCgpICU+JSBhZGRUaWxlcygpICU+JSBzZXRWaWV3KGxuZyA9IC05OC41Nzk1LCBsYXQgPSAzOS44MjgzLCB6b29tID0gMylgLgpcClRoZSBvbmUgdGhpbmcgdGhhdCBzaG91bGQgYmUgbm90ZWQgaXMgdGhhdCB0aGUgZGVmYXVsdCB2aWV3IHdhcyB0YWtlbiBhcyB0aGUgY2VudGVyIHBvaW50IG9mIHRoZSBVU0EuIFNpbmNlIG91ciByZWR1Y2VkIGRhdGEgd2FzIHJhbmRvbWx5IHRha2VuIGZyb20gdGhlIG9yaWdpbmFsIDcwSyByb3dzLCB1c2luZyB0aGUgbWVhbiBvZiB0aGUgbG5nIGFuZCBsYXQgdmFsdWVzIHdpbGwgbm90IGdpdmUgdXMgYSBjb25zaXN0ZW50bHkgZ29vZCB2aWV3LCBiZWNhdXNlIHRoZSB2aWV3IGlzIHRoZW4gc3ViamVjdCB0byB3aGF0IGdhcyBzdGF0aW9ucyB3ZSBoYXZlIHJhbmRvbWx5IGNob3Nlbi4gVG8gY2lyY3VtdmVudCB0aGlzLCB0aGUgY2VudGVyIHBvaW50IG9mIHRoZSBVU0Egd2FzIHVzZWQuIAoKQXNpZGUgdG8gdGhhdCwgd2UgdGhlbiB1c2UgYSBiYXNpYyBgZm9yYCBsb29wIGNvbnRyb2wgc3RydWN0dXJlLCBpbiBjb21iaW5hdGlvbiB3aXRoIHRoZSBgcGFzdGUoLi4uKWAgZnVuY3Rpb24gdG8gYWRkIGxvY2F0aW9uIG1ldGFkYXRhIHVzaW5nIHRoZSBgY2l0eWAsIGBzdGF0ZWAsIGBjb3VudHlgLCBhbmQgYGFkZHJlc3NgIGZyb20gb3VyIGRhdGEgc2V0LgoKVGhpcyBpcyBhIHByaW1pdGl2ZSBleGFtcGxlIG9mIGhvdyB0aGUgYGxlYWZsZXRgIGxpYnJhcnkgY2FuIGJlIHVzZWQgdG8gZ3JhcGggZ2VvZ3JhcGhpY2FsIGluZm9ybWF0aW9uLiBMZXQncyBtb3ZlIG9udG8gYSBtb3JlIGFkdmFuY2VkIGV4YW1wbGUgc28gdGhhdCB3ZSBhcmUgYWJsZSB0byBleHRyYWN0IG1vcmUgdXNlZnVsIGluZm9ybWF0aW9uIGFuZCBkZWNpcGhlciBhIHN0b3J5IHRoZXJlaW4uCgojIyBQaGlsbHkgQ3JpbWUgRGF0YQoKRmlyc3QsIHdlIHdpbGwgbmVlZCBhIG5ldyBkYXRhIHNldCB0aGF0IGlzIGhvc3RlZCBvbiBteSBnaXRodWIuIAoKYGBge3IgZWNobz1UUlVFfQpwaGlsbHlfZGF0YSA8LSByZWFkLmNzdigiaHR0cHM6Ly9qbWFydGluMTIuZ2l0aHViLmlvL1NUQVQ1NTMvZGF0YS9QaGlsbHlDcmltZVNpbmNlMjAxNS5jc3YiLCBoZWFkZXIgPSBUUlVFKQpgYGAKCkFuIGV4YW1wbGUgZmV3IHJvd3MgYXJlIGdpdmVuOiAKCmBgYHtyIGVjaG89RkFMU0V9CmhlYWQocGhpbGx5X2RhdGEsIDIpCmBgYAoKVGhlIHRvdGFsIGFtb3VudCBvZiByb3dzIGFyZTogCgpgYGB7ciBlY2hvPUZBTFNFfQpucm93KHBoaWxseV9kYXRhKQpgYGAKClBoaWxseSBpcyBkZWZpbml0ZWx5IGtub3duIGZvciBjcmltZSBpbiBjZXJ0YWluIGFyZWFzLiBMZXQncyBmb2N1cyBpbiBvbiB0aGUgeWVhciAyMDIzLiBUbyBhY2NvbXBsaXNoIHRoaXMsIHdlIHdpbGwgaGF2ZSB0byBwZXJmb3JtIHNvbWUgZGF0YSB0cmFuc2Zvcm1hdGlvbnMgYWdhaW5zdCB0aGUgb3JpZ2luYWwgYGRhdGVgIGNvbHVtbi4gRmlyc3QsIHRoZSBmb2xsb3dpbmcgY29kZSBwYXJzZXMgb3V0IHRoZSBgeWVhcmAgcG9ydGlvbiwgYW5kIHN0b3JlcyBpdCBpbiBhIHZlY3Rvci4KCgpgYGB7ciBlY2hvPVRSVUV9CnllYXJzIDwtIGNoYXJhY3Rlcihucm93KHBoaWxseV9kYXRhKSkKCmZvciAoaSBpbiAxOm5yb3cocGhpbGx5X2RhdGEpKSB7CiAgZGF0ZV9zdHJpbmcgPC0gcGhpbGx5X2RhdGEkZGF0ZVtpXQogIGRhdGVfb2JqZWN0IDwtIHN0cnB0aW1lKGRhdGVfc3RyaW5nLCAiJW0vJWQvJVkgJUg6JU0iKQogIHllYXJzW2ldIDwtIGZvcm1hdChkYXRlX29iamVjdCwgIiVZIikKfQoKaGVhZCh5ZWFycykKYGBgCgpHcmVhdCEgTm93IHdlIGhhdmUgYWxsIHRoZSB5ZWFycy4gTGV0J3MgYWRkIGl0IGFzIGEgbmV3IGNvbHVtbiB0byBvdXIgb3JpZ2luYWwgcGhpbGx5IGNyaW1lIGRhdGFzZXQuCgpgYGB7ciBlY2hvPVRSVUV9CnBoaWxseV9kYXRhJHllYXIgPC0geWVhcnMKaGVhZChwaGlsbHlfZGF0YSwgMikKYGBgCgpFYXN5IGFzIHRoYXQhIFdlIGNhbiBzZWUgdGhlIHllYXIgY29sdW1uIGlzIG5vdyBhZGRlZCB0byB0aGUgZGF0YSBzZXQuIApUbyBuYXJyb3cgZG93biBhIHN1YnNldCBvZiAyMDIzIGRhdGEgb25seSwgd2UgY2FuIHJlZHVjZSBvdXIgZGF0YSBzZXQgYnkgZmlsdGVyaW5nIG9uIHRoZSBuZXdseSBhZGRlZCBgeWVhcmAgY29sdW1uLgoKYGBge3IgZWNobz1UUlVFfQpwaGlsbHlfZGF0YV8yMDIzIDwtIHN1YnNldChwaGlsbHlfZGF0YSwgeWVhciA9PSAyMDIzKQpoZWFkKHBoaWxseV9kYXRhXzIwMjMkZGF0ZSwgNSkKYGBgCgpPbmx5IHRoZSBgZGF0ZWAgY29sdW1uIGlzIHNob3duIGluIHRoZSBhYm92ZSBleGFtcGxlIC0tIHRoZSByZW1haW5pbmcgY29sdW1ucyBhcmUgc3RpbGwgc3RvcmVkIGluIG1lbW9yeS4gV2UgY2FuIGNsZWFybHkgc2VlIHRoZXJlIGFyZSBvbmx5IGRhdGVzIGluIGAyMDIzYC4gClwKCiMjIFBoaWxseSBDcmltZXMgLSBNYXBwZWQKTm93IHRoYXQgd2UgaGF2ZSBvdXIgMjAyMyBkYXRhLCBsZXQncyBnbyBhaGVhZCBhbmQgdXNlIGxlYWZseSB0byBtYXAgb3VyIGRhdGEuIFRoZSBjb2RlIGJlbG93IGFkZHMgdHdvIGFkZGl0aW9uYWwgY29sdW1ucyB0byB0aGUgZGF0YXNldCBzbyB3ZSBjYW4gZWFzaWx5IGRldGVybWluZSB3aGF0IGNvbG9ycyB0byB1c2UsIGFuZCBpbiBhZGRpdGlvbiBnZW5lcmF0aW5nIHRoZSBsYWJlbCBpbmZvcm1hdGlvbi4gCgpGcm9tIHRoZXJlLCBhIHRpdGxlIGFsb25nIHdpdGggYSBsZWdlbmQgaXMgYWRkZWQgdG8gdGhlIGdyYXBoLiAKCmBgYHtyIGVjaG89VFJVRX0KY29sb3JzIDwtIGNoYXJhY3Rlcihucm93KHBoaWxseV9kYXRhXzIwMjMpKQpsYWJlbHMgPC0gY2hhcmFjdGVyKG5yb3cocGhpbGx5X2RhdGFfMjAyMykpCiAKZm9yIChpIGluIDE6bnJvdyhwaGlsbHlfZGF0YV8yMDIzKSkgewogICMgSGFuZGxlIHRoZSBjb2xvcnMKICBpZiAocGhpbGx5X2RhdGFfMjAyMyRmYXRhbFtpXSA9PSAnRmF0YWwnKSB7CiAgICBjb2xvcnNbaV0gPC0gJyMwMDAwMDAnCiAgfQogIGVsc2UgewogICAgY29sb3JzW2ldIDwtICcjQ0M3OUE3JyAgICAKICB9CiAgCiAgIyBIYW5kbGUgdGhlIGluZm8gdG8gZGlzcGxheSBpbiB0aGUgbGFiZWwKICBsYWJlbHNbaV0gPC0gcGFzdGUoJ0dlbmRlcjogJywgcGhpbGx5X2RhdGFfMjAyMyRzZXhbaV0sICdOZWlnaGJvcmhvb2Q6ICcsIHBoaWxseV9kYXRhXzIwMjMkbmVpZ2hib3Job29kW2ldLCAnQmxvY2sgTnVtYmVyOiAnLCBwaGlsbHlfZGF0YV8yMDIzJGJsb2NrX251bWJlcltpXSkKfQoKcGhpbGx5X2RhdGFfMjAyMyRjcmltZV90eXBlX2NvbG9yIDwtIGNvbG9ycwpwaGlsbHlfZGF0YV8yMDIzJGxhYmVsIDwtIGxhYmVscwoKIyMgTWFwIHRpdGxlCnRpdGxlIDwtIHRhZ3MkZGl2KCBIVE1MKCc8Zm9udCBjb2xvciA9ICJkYXJrcmVkIiBzaXplID00PjxiPlBoaWxseSBGYXRhbCBhbmQgTm9uLUZhdGFsIENyaW1lcyAyMDIzPC9iPjwvZm9udD4nKQopCgojIENyZWF0ZSBhIExlYWZsZXQgbWFwCm0gPC0gbGVhZmxldChwaGlsbHlfZGF0YV8yMDIzKSAlPiUKICBhZGRUaWxlcygpICU+JQogIHNldFZpZXcobGF0ID0gNDAsIGxuZyA9IC03NS4xNjUyLCB6b29tID0gMTEpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoCiAgICBsbmcgPSB+bG5nLAogICAgbGF0ID0gfmxhdCwKICAgIGNvbG9yID0gfmNyaW1lX3R5cGVfY29sb3IsCiAgICByYWRpdXMgPSAzLAogICAgbGFiZWwgPSB+bGFiZWwsCiAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gRkFMU0UsIHRleHRPbmx5ID0gRkFMU0UpKSAlPiUKICBhZGRDb250cm9sKHRpdGxlLCBwb3NpdGlvbiA9ICJ0b3ByaWdodCIpICU+JQogIGFkZExlZ2VuZChwb3NpdGlvbiA9ICJib3R0b21sZWZ0IiwgCiAgICAgICAgICAgIGNvbG9ycyA9IGMoIiNDQzc5QTciLCAiIzAwMDAwMCIpLAogICAgICAgICAgICBsYWJlbHM9IGMoIk5vbiBGYXRhbCIsICJGYXRhbCIpLAogICAgICAgICAgICB0aXRsZT0gIkNyaW1lIFR5cGUiLAogICAgICAgICAgICBvcGFjaXR5ID0gMC44KQoKbQpgYGAKClwKXAoKIyMgTmFycmF0aW9uCgpJdCBpcyBwYXJ0aWN1bGFybHkgaW50ZXJlc3RpbmcgdG8gc2VlIHRoZSBkaXN0cmlidXRpb24gb2YgY3JpbWVzIGFjcm9zcyB0aGUgY2l0eSBvZiBQaGlsbHkuIFRoZXJlIGFyZSBhIGZldyBob3RzcG90cyBvZiBmYXRhbCBjcmltZXMuIFRoZSBwcmltYXJ5IGhvdHNwb3QgYmVpbmcgdGhlIHNvdXRod2VzdCByZWdpb24gb2YgdGhlIGNpdHksIGZvbGxvd2VkIGJ5IGEgc21hbGxlciBob3RzcG90IGp1c3Qgbm9ydGggb2YgdGhlIGNpdHkuCgpUaGUgZGF0YXNldCBpdHNlbGYgY2FuIHRlbGwgdXMgbW9yZSB3aXRoIGRpZmZlcmVudCBncmFwaHMuIExldCdzIHZpZXcgdGhlICUgb2YgY3JpbWVzIGNvbW1pdHRlZCBieSBnZW5kZXIuCgpgYGAge3IgZWNobz1UUlVFfQojIENvdW50IHRoZSBudW1iZXIgb2YgY3JpbWVzIHBlciBnZW5kZXIKY3JpbWVzX3Blcl9nZW5kZXIgPC0gcGhpbGx5X2RhdGFfMjAyMyAlPiUKICBncm91cF9ieShzZXgpICU+JQogIHN1bW1hcmlzZShjcmltZV9jb3VudCA9IG4oKSkKCiMgQ2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIGVhY2ggZ2VuZGVyCmNyaW1lc19wZXJfZ2VuZGVyJHBlcmNlbnRhZ2UgPC0gKGNyaW1lc19wZXJfZ2VuZGVyJGNyaW1lX2NvdW50IC8gc3VtKGNyaW1lc19wZXJfZ2VuZGVyJGNyaW1lX2NvdW50KSkgKiAxMDAKCiMgQ3JlYXRlIGEgcGllIGNoYXJ0CnAzIDwtIHBsb3RfbHkoY3JpbWVzX3Blcl9nZW5kZXIsIGxhYmVscyA9IH5zZXgsIHZhbHVlcyA9IH5wZXJjZW50YWdlLCB0eXBlID0gJ3BpZScpICU+JQogIGxheW91dCh0aXRsZSA9ICIlIG9mIENyaW1lcyBDb21taXR0ZWQgYnkgTWFsZXMgdnMuIEZlbWFsZXMiKQoKIyBQcmludCB0aGUgcGxvdApwMwpgYGAKCgoKSXQgc2VlbXMgdGhhdCBtYWxlcyBjb21taXQgbW9zdCBvZiB0aGUgY3JpbWVzIGluIHRoZSBjaXR5IG9mIFBoaWxhZGVscGhpYS4gU29tZXRoaW5nIGVsc2Ugb2YgaW50ZXJlc3QgY291bGQgYmUgdG8gYW5hbHl6ZSB3aGljaCBzY2hvb2wgZGlzdHJpY3RzIGhhdmUgdGhlIGhpZ2hlc3QgY3JpbWUgY291bnQuIEluIHBhcnRpY3VsYXIsIHBhcmVudHMgY291bGQgZmluZCB0aGlzIGRhdGEgdG8gYmUgb2YgdXNlIHdoZW4gZGV0ZXJtaW5pbmcgd2hhdCBzY2hvb2wgZGlzdHJpY3QgdG8gc2VuZCB0aGVpciBraWRzIHRvLiAgIAoKYGBge3IgZWNobz1UUlVFfQogICMgQ291bnQgdGhlIG51bWJlciBvZiBjcmltZXMgcGVyIHNjaG9vbCBkaXN0cmljdAogIGNyaW1lc19wZXJfZGlzdHJpY3QgPC0gcGhpbGx5X2RhdGFfMjAyMyAlPiUKICAgIGdyb3VwX2J5KHNjaG9vbF9jYXRjaG1lbnQpICU+JQogICAgc3VtbWFyaXNlKGNyaW1lX2NvdW50ID0gbigpKSAlPiUKICAgIHRvcF9uKDEwLCBjcmltZV9jb3VudCkKCiAgCiAgIyBDcmVhdGUgYSBiYXIgZ3JhcGgKICBwIDwtIHBsb3RfbHkoY3JpbWVzX3Blcl9kaXN0cmljdCwgeSA9IH5zY2hvb2xfY2F0Y2htZW50LCB4ID0gfmNyaW1lX2NvdW50LCB0eXBlID0gJ2JhcicpICU+JQogICAgbGF5b3V0KHRpdGxlID0gIk51bWJlciBvZiBDcmltZXMgcGVyIFNjaG9vbCBEaXN0cmljdCBpbiBQaGlsYWRlbHBoaWEgKDIwMjMpIiwKICAgICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiU2Nob29sIERpc3RyaWN0IiksCiAgICAgICAgICAgeWF4aXMgPSBsaXN0KCh0aXRsZSA9ICJOdW1iZXIgb2YgQ3JpbWVzIikpKQogIAogICMgUHJpbnQgdGhlIHBsb3QKICBwCiAgCmBgYAoKVGhlc2UgdGVuIHNjaG9vbHMgYXJlIHRoZSBzY2hvb2xzIHdpdGggdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIHJlcG9ydGVkIGNyaW1lcywgaW5kaWNhdGluZyBhIHBvdGVudGlhbGx5IGhpZ2hlciBjcmltZSByYXRlIGluIHRoZXNlIGFyZWFzLiBUaGlzIGRhdGEgY291bGQgYmUgaW5mbHVlbnRpYWwgd2hlbiBpbmZvcm1pbmcgcGFyZW50cyBhYm91dCB0aGUgc2FmZXR5IHJpc2tzIGFzc29jaWF0ZWQgd2l0aCBhIHBhcnRpY3VsYXIgc2Nob29sIGRpc3RyaWN0LCBoZWxwaW5nIHRoZW0gbWFrZSBpbmZvcm1lZCBkZWNpc2lvbnMgYWJvdXQgdGhlaXIgY2hpbGRyZW4ncyBlZHVjYXRpb24gYW5kIHdlbGwtYmVpbmcuCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg==